#include "wx/wxprec.h"

#if wxUSE_CLIPBOARD

#include "wx/clipbrd.h"
#include "wx/gtk/private.h"

GdkAtom  g_clipboardAtom   = 0;
GdkAtom  g_targetsAtom     = 0;
GdkAtom  g_timestampAtom   = 0;
extern GdkAtom g_altTextAtom;

#define TRACE_CLIPBOARD _T("clipboard")

extern "C" {
  static void
  targets_selection_received( GtkWidget *( widget ),
                              GtkSelectionData *selection_data,
                              guint32 ( time ),
                              wxClipboard *clipboard ) {
    if( wxTheClipboard && selection_data->length > 0 ) {
      // make sure we got the data in the correct form
      GdkAtom type = selection_data->type;
      if( type != GDK_SELECTION_TYPE_ATOM ) {
        if( strcmp( wxGtkString( gdk_atom_name( type ) ), "TARGETS" ) ) {
          wxLogTrace( TRACE_CLIPBOARD,
                      _T( "got unsupported clipboard target" ) );
          clipboard->m_waiting = false;
          return;
        }
      }
      #ifdef __WXDEBUG__
      wxDataFormat clip( selection_data->selection );
      wxLogTrace( TRACE_CLIPBOARD,
                  wxT( "selection received for targets, clipboard %s" ),
                  clip.GetId().c_str() );
      #endif // __WXDEBUG__
      // the atoms we received, holding a list of targets (= formats)
      GdkAtom *atoms = ( GdkAtom * )selection_data->data;
      for( unsigned int i = 0; i < selection_data->length / sizeof( GdkAtom ); i++ ) {
        wxDataFormat format( atoms[i] );
        wxLogTrace( TRACE_CLIPBOARD,
                    wxT( "selection received for targets, format %s" ),
                    format.GetId().c_str() );
        //            printf( "format %s requested %s\n",
        //                    gdk_atom_name( atoms[i] ),
        //                    gdk_atom_name( clipboard->m_targetRequested ) );
        if( format == clipboard->m_targetRequested ) {
          clipboard->m_waiting = false;
          clipboard->m_formatSupported = true;
          return;
        }
      }
    }
    clipboard->m_waiting = false;
  }
}

//-----------------------------------------------------------------------------
// "selection_received" for the actual data
//-----------------------------------------------------------------------------

extern "C" {
  static void
  selection_received( GtkWidget *( widget ),
                      GtkSelectionData *selection_data,
                      guint32 ( time ),
                      wxClipboard *clipboard ) {
    if( !wxTheClipboard ) {
      clipboard->m_waiting = false;
      return;
    }
    wxDataObject *data_object = clipboard->m_receivedData;
    if( !data_object ) {
      clipboard->m_waiting = false;
      return;
    }
    if( selection_data->length <= 0 ) {
      clipboard->m_waiting = false;
      return;
    }
    wxDataFormat format( selection_data->target );
    if( !data_object->IsSupportedFormat( format ) ) {
      clipboard->m_waiting = false;
      return;
    }
    data_object->SetData( format, ( size_t ) selection_data->length, ( const char* ) selection_data->data );
    wxTheClipboard->m_formatSupported = true;
    clipboard->m_waiting = false;
  }
}

extern "C" {
  static gint
  selection_clear_clip( GtkWidget *( widget ), GdkEventSelection *event ) {
    if( !wxTheClipboard ) {
      return true;
    }
    if( event->selection == GDK_SELECTION_PRIMARY ) {
      wxTheClipboard->m_ownsPrimarySelection = false;
    } else if( event->selection == g_clipboardAtom ) {
      wxTheClipboard->m_ownsClipboard = false;
    } else {
      wxTheClipboard->m_waiting = false;
      return FALSE;
    }
    if( ( !wxTheClipboard->m_ownsPrimarySelection ) &&
        ( !wxTheClipboard->m_ownsClipboard ) ) {
      /* the clipboard is no longer in our hands. we can the delete clipboard data. */
      if( wxTheClipboard->m_data ) {
        wxLogTrace( TRACE_CLIPBOARD, wxT( "wxClipboard will get cleared" ) );
        delete wxTheClipboard->m_data;
        wxTheClipboard->m_data = ( wxDataObject* ) NULL;
      }
    }
    wxTheClipboard->m_waiting = false;
    return TRUE;
  }
}

//-----------------------------------------------------------------------------
// selection handler for supplying data
//-----------------------------------------------------------------------------

extern "C" {
  static void
  selection_handler( GtkWidget *( widget ),
                     GtkSelectionData *selection_data,
                     guint ( info ),
                     guint ( time ),
                     gpointer signal_data ) {
    if( !wxTheClipboard ) {
      return;
    }
    if( !wxTheClipboard->m_data ) {
      return;
    }
    wxDataObject *data = wxTheClipboard->m_data;
    if( selection_data->target == g_timestampAtom ) {
      guint timestamp = GPOINTER_TO_UINT( signal_data );
      gtk_selection_data_set( selection_data,
                              GDK_SELECTION_TYPE_INTEGER,
                              32,
                              ( guchar* ) & ( timestamp ),
                              sizeof( timestamp ) );
      wxLogTrace( TRACE_CLIPBOARD,
                  _T( "Clipboard TIMESTAMP requested, returning timestamp=%u" ),
                  timestamp );
      return;
    }
    wxDataFormat format( selection_data->target );
    #ifdef __WXDEBUG__
    wxLogTrace( TRACE_CLIPBOARD,
                _T( "clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u" ),
                format.GetId().c_str(),
                wxString::FromAscii( wxGtkString( gdk_atom_name( selection_data->target ) ) ).c_str(),
                wxString::FromAscii( wxGtkString( gdk_atom_name( selection_data->type ) ) ).c_str(),
                wxString::FromAscii( wxGtkString( gdk_atom_name( selection_data->selection ) ) ).c_str(),
                GPOINTER_TO_UINT( signal_data )
              );
    #endif
    if( !data->IsSupportedFormat( format ) ) {
      return;
    }
    int size = data->GetDataSize( format );
    if( size == 0 ) {
      return;
    }
    wxCharBuffer buf( size );
    data->GetDataHere( format, buf.data() );
    if( format == wxDataFormat( wxDF_UNICODETEXT ) ) {
      gtk_selection_data_set_text(
        selection_data,
        ( const gchar* )buf.data(),
        size );
    } else {
      gtk_selection_data_set(
        selection_data,
        GDK_SELECTION_TYPE_STRING,
        8 * sizeof( gchar ),
        ( const guchar* )buf.data(),
        size );
    }
  }
}

IMPLEMENT_DYNAMIC_CLASS( wxClipboard, wxObject )

wxClipboard::wxClipboard() {
  m_open = false;
  m_waiting = false;
  m_ownsClipboard = false;
  m_ownsPrimarySelection = false;
  m_data = ( wxDataObject* ) NULL;
  m_receivedData = ( wxDataObject* ) NULL;
  /* we use m_targetsWidget to query what formats are available */
  m_targetsWidget = gtk_window_new( GTK_WINDOW_POPUP );
  gtk_widget_realize( m_targetsWidget );
  g_signal_connect( m_targetsWidget, "selection_received",
                    G_CALLBACK( targets_selection_received ), this );
  /* we use m_clipboardWidget to get and to offer data */
  m_clipboardWidget = gtk_window_new( GTK_WINDOW_POPUP );
  gtk_widget_realize( m_clipboardWidget );
  g_signal_connect( m_clipboardWidget, "selection_received",
                    G_CALLBACK( selection_received ), this );
  g_signal_connect( m_clipboardWidget, "selection_clear_event",
                    G_CALLBACK( selection_clear_clip ), NULL );
  if( !g_clipboardAtom ) {
    g_clipboardAtom = gdk_atom_intern( "CLIPBOARD", FALSE );
  }
  if( !g_targetsAtom ) {
    g_targetsAtom = gdk_atom_intern( "TARGETS", FALSE );
  }
  if( !g_timestampAtom ) {
    g_timestampAtom = gdk_atom_intern( "TIMESTAMP", FALSE );
  }
  m_formatSupported = false;
  m_targetRequested = 0;
  m_usePrimary = false;
}

wxClipboard::~wxClipboard() {
  Clear();
  if( m_clipboardWidget ) {
    gtk_widget_destroy( m_clipboardWidget );
  }
  if( m_targetsWidget ) {
    gtk_widget_destroy( m_targetsWidget );
  }
}

void wxClipboard::Clear() {
  gtk_selection_clear_targets( m_clipboardWidget, m_usePrimary ? GDK_SELECTION_PRIMARY : g_clipboardAtom );
  if( m_data ) {
    #if wxUSE_THREADS
    /* disable GUI threads */
    #endif
    //  As we have data we also own the clipboard. Once we no longer own
    //  it, clear_selection is called which will set m_data to zero
    if( gdk_selection_owner_get( g_clipboardAtom ) == m_clipboardWidget->window ) {
      m_waiting = true;
      gtk_selection_owner_set( ( GtkWidget* ) NULL, g_clipboardAtom,
                               ( guint32 ) GDK_CURRENT_TIME );
      while( m_waiting ) {
        gtk_main_iteration();
      }
    }
    if( gdk_selection_owner_get( GDK_SELECTION_PRIMARY ) == m_clipboardWidget->window ) {
      m_waiting = true;
      gtk_selection_owner_set( ( GtkWidget* ) NULL, GDK_SELECTION_PRIMARY,
                               ( guint32 ) GDK_CURRENT_TIME );
      while( m_waiting ) {
        gtk_main_iteration();
      }
    }
    delete m_data;
    m_data = NULL;
    #if wxUSE_THREADS
    /* re-enable GUI threads */
    #endif
  }
  m_targetRequested = 0;
  m_formatSupported = false;
}

bool wxClipboard::Open() {
  wxCHECK_MSG( !m_open, false, wxT( "clipboard already open" ) );
  m_open = true;
  return true;
}

bool wxClipboard::SetData( wxDataObject *data ) {
  wxCHECK_MSG( m_open, false, wxT( "clipboard not open" ) );
  wxCHECK_MSG( data, false, wxT( "data is invalid" ) );
  Clear();
  return AddData( data );
}

bool wxClipboard::AddData( wxDataObject *data ) {
  wxCHECK_MSG( m_open, false, wxT( "clipboard not open" ) );
  wxCHECK_MSG( data, false, wxT( "data is invalid" ) );
  // we can only store one wxDataObject
  Clear();
  m_data = data;
  // get formats from wxDataObjects
  wxDataFormat *array = new wxDataFormat[ m_data->GetFormatCount() ];
  m_data->GetAllFormats( array );
  // primary selection or clipboard
  GdkAtom clipboard = m_usePrimary ? ( GdkAtom )GDK_SELECTION_PRIMARY
                      : g_clipboardAtom;
  // by default provide TIMESTAMP as a target
  gtk_selection_add_target( GTK_WIDGET( m_clipboardWidget ),
                            clipboard,
                            g_timestampAtom,
                            0 );
  for( size_t i = 0; i < m_data->GetFormatCount(); i++ ) {
    wxLogTrace( TRACE_CLIPBOARD,
                wxT( "wxClipboard now supports atom %s" ),
                array[i].GetId().c_str() );
    gtk_selection_add_target( GTK_WIDGET( m_clipboardWidget ),
                              clipboard,
                              array[i],
                              0 );  /* what is info ? */
  }
  delete[] array;
  g_signal_connect( m_clipboardWidget, "selection_get",
                    G_CALLBACK( selection_handler ),
                    GUINT_TO_POINTER( gtk_get_current_event_time() ) );
  #if wxUSE_THREADS
  /* disable GUI threads */
  #endif
  /* Tell the world we offer clipboard data */
  bool res = ( gtk_selection_owner_set( m_clipboardWidget,
                                        clipboard,
                                        ( guint32 ) GDK_CURRENT_TIME ) );
  if( m_usePrimary ) {
    m_ownsPrimarySelection = res;
  } else
  { m_ownsClipboard = res; }
  #if wxUSE_THREADS
  /* re-enable GUI threads */
  #endif
  return res;
}

void wxClipboard::Close() {
  wxCHECK_RET( m_open, wxT( "clipboard not open" ) );
  m_open = false;
}

bool wxClipboard::IsOpened() const {
  return m_open;
}

bool wxClipboard::IsSupported( const wxDataFormat& format ) {
  /* reentrance problems */
  if( m_waiting ) {
    return false;
  }
  /* store requested format to be asked for by callbacks */
  m_targetRequested = format;
  wxLogTrace( TRACE_CLIPBOARD,
              wxT( "wxClipboard:IsSupported: requested format: %s" ),
              format.GetId().c_str() );
  wxCHECK_MSG( m_targetRequested, false, wxT( "invalid clipboard format" ) );
  m_formatSupported = false;
  m_waiting = true;
  gtk_selection_convert( m_targetsWidget,
                         m_usePrimary ? ( GdkAtom )GDK_SELECTION_PRIMARY
                         : g_clipboardAtom,
                         g_targetsAtom,
                         ( guint32 ) GDK_CURRENT_TIME );
  while( m_waiting ) {
    gtk_main_iteration();
  }
  if( !m_formatSupported && format == wxDataFormat( wxDF_UNICODETEXT ) ) {
    extern GdkAtom g_altTextAtom;
    return IsSupported( g_altTextAtom );
  }
  return m_formatSupported;
}

bool wxClipboard::GetData( wxDataObject& data ) {
  wxCHECK_MSG( m_open, false, wxT( "clipboard not open" ) );
  /* get formats from wxDataObjects */
  wxDataFormat *array = new wxDataFormat[ data.GetFormatCount() ];
  data.GetAllFormats( array );
  for( size_t i = 0; i < data.GetFormatCount(); i++ ) {
    wxDataFormat format( array[i] );
    wxLogTrace( TRACE_CLIPBOARD,
                wxT( "wxClipboard::GetData: requested format: %s" ),
                format.GetId().c_str() );
    m_targetRequested = format;
    wxCHECK_MSG( m_targetRequested, false, wxT( "invalid clipboard format" ) );
    m_formatSupported = false;
    m_waiting = true;
    gtk_selection_convert( m_targetsWidget,
                           m_usePrimary ? ( GdkAtom )GDK_SELECTION_PRIMARY
                           : g_clipboardAtom,
                           g_targetsAtom,
                           ( guint32 ) GDK_CURRENT_TIME );
    while( m_waiting ) {
      gtk_main_iteration();
    }
    if( !m_formatSupported ) {
      continue;
    }
    m_receivedData = &data;
    m_targetRequested = format;
    wxCHECK_MSG( m_targetRequested, false, wxT( "invalid clipboard format" ) );
    /* start query */
    m_formatSupported = false;
    m_waiting = true;
    wxLogTrace( TRACE_CLIPBOARD,
                wxT( "wxClipboard::GetData: format found, start convert" ) );
    gtk_selection_convert( m_clipboardWidget,
                           m_usePrimary ? ( GdkAtom )GDK_SELECTION_PRIMARY
                           : g_clipboardAtom,
                           m_targetRequested,
                           ( guint32 ) GDK_CURRENT_TIME );
    while( m_waiting ) {
      gtk_main_iteration();
    }
    {
      wxCHECK_MSG( m_formatSupported, false,
                   wxT( "error retrieving data from clipboard" ) );
    }
    delete[] array;
    return true;
  }
  wxLogTrace( TRACE_CLIPBOARD,
              wxT( "wxClipboard::GetData: format not found" ) );
  delete[] array;
  return false;
}

#endif
